/**
 * @Description JSpider 是一个用 javascript编写的用于快速网页爬取的类
 * @Author KonghaYao
 * @Date 2020/8/19
 * @LastEditTime 2020/8/19
 * @LastEditors KonghaYao
 * @license MIT
 */

class JSpider {
  /**
   * @typedef {Object} requestOptions 这是请求的基础参数
   * @property {Array} urls 定义请求url的数组
   * @property {Object} [options={}] 定义默认请求选项
   * @property {Number} [limits=3] 定义并发请求数
   * @property {Number} [time=200] 定义定时请求间隔时间
   * @property {String} [type=test] 定义批量网络请求的形式
   */
  /**
   * 创建一个JSpide类用于定义请求的参数注意下面所有的参数需要放置在一个 对象中
   * @constructor
   * @param {Object} requestOptions 详细查看requestOptions
   * @type {requestOptions}
   */
  constructor({
    urls,
    options = {
      method: "get",
    },
    limits = 3,
    time = 200,
    type = "test",
  }) {
    this.requestOptions = {
      urls: urls,
      options: options,
      limits: limits,
      type: type,
      time: time,
    };
    console.log("载入成功");
  }
  /**
   * 请求入口函数
   * @method ajax
   * @date 2020-08-19
   * @type requestOptions
   * @param {Object} [requestOptions] 可以覆盖contryctor 定义的请求参数
   * <br>如果在初始化实例的时候已经建立过了
   * <br>这里可以覆盖
   * @returns {Object} 返回 result 数组
   */
  async ajax(requestOptions) {
    let { urls = this.requestOptions.urls, time = this.requestOptions.time, options = this.requestOptions.options, type = this.requestOptions.type, limits = this.requestOptions.limits } = requestOptions || this.requestOptions;
    switch (type) {
      case "time":
        this.result = await this.requestTime(urls, options, time);
        return this.result;
      case "sync":
        console.log("%c 并发");
        this.result = await this.requestSync(urls, options, limits);
        console.log("%c 请求完成", "color:green");
        return this.result;
      case "async":
        console.log("%c 异步队列");
        this.result = await this.requestSync(urls, options, 1);
        console.log("%c 请求完成", "color:green");
        return this.result;
      default:
        console.log("%c 测试");
        let res = await Promise.all(
          [0, 1, 2].map((i) => {
            if (urls[i]) {
              return this.request(urls[i], options);
            }
          })
        ).then((res) => res);
        return res;
    }
  }

  /**
   * 定时请求  将数组形式的 URL 组请求 使用window.result收集结果
   * @method requestTime
   * @date 2020-08-18
   * @param {Array} arr 请求 URL 数组
   * @param {Number} [msNum=100] 请求间隔
   * @returns {Promise}
   */
  requestTime(arr, options, msNum = 100) {
    return Promise.all(
      arr.map(
        (i, index) =>
          new Promise((resolve, reject) => {
            setTimeout(
              async (url, options) => {
                let res = await this.request(url, options);
                console.log("Time: " + new Date().getTime());
                resolve(res);
              },
              index * msNum,
              arr[index],
              options
            );
          })
      )
    );
  }
  /**
   * 并发请求
   * @method requestSync
   * @date 2020-08-18
   * @description 并发限制为 1 时作为队列请求
   * @param {Array} urls URL 数组
   * @param {Object} [options={}] fetch 的options
   * @param {Number} [limits=3] 每次同时请求个数
   * @returns {Promise} 返回请求结果数组
   */
  requestSync(urls, options = {}, limits = 3) {
    let num = Math.ceil(urls.length / limits);
    let func = async (arr) => {
      let [urls, options, result] = arr;
      let group = urls.splice(0, limits);
      let res = await Promise.all(group.map((url) => this.request(url, options)));
      console.log(`${limits} 个已完成` + new Date().getTime());
      result.push(res);
      return [urls, options, result];
    };
    return Array(num)
      .fill(func)
      .reduce((next, current) => {
        return next.then(current);
      }, Promise.resolve([urls, options, []]))
      .then((res) => res.pop());
  }

  /**
   * 请求封装
   * @method request
   * @date 2020-08-18
   * @param {String} url
   * @param {Object} options
   * @returns {Promise}
   */
  request(url, options) {
    if (typeof url === "object") {
      options = url.options(options);
      url = url.url;
    }
    return new Promise((resolve, reject) => {
      fetch(url, options)
        .then((res) => res.text())
        .then((res) => resolve(res))
        .catch((err) => reject(err));
    });
  }

  /**
   * 解析函数
   * @method parse
   * @date 2020-08-19
   * @param {Array} parseList
   * @param {function} parsefunc 可以处理(dom)=>dom
   *
   * @returns {Array} 返回一个包含结果的数组
   * @example
   * parse(Spider.result,(dom)=>{
   *      dom.querySelectorAll('a').forEach(i=>i.remove())
   *      return dom
   * })
   */
  parse(parseList, parsefunc) {
    this.parseResult = parseList.map((i) => {
      let body = i.match(/(?<=\<body[\s\S]*?>)[\s\S]+(?=<\/body>)/)[0];
      let doc = document.createElement("div");
      doc.innerHTML = body;
      let result = parsefunc(doc);
      console.log("%c 解析完成", "color:green");
      doc.remove();
      return result;
    });
    console.log(this.parseResult);
    return [this.parseResult];
  }

  /**
   * 下载器 函数
   * @method download
   * @date 2020-08-19
   * @param {Array} fileList 文件数组
   * @param {Array} [nameList] 名称数组
   * @example
   * download(['中文',23333,new Blob(['text']),new File(['233']),...[...input.files]])
   * //这里可以放置很多种类型的数据
   */
  async download(fileList, nameList) {
    //处理 下载文件的名称
    let num = fileList.length;
    nameList = nameList || [...Array(num).keys()].map((i) => i + ".txt");
    console.log("下载个数 ： " + num);
    // 处理下载数组个数不等
    if (nameList.length !== num) {
      console.log("%c 文件名数组个数：" + num + "  与文件个数不等 下载取消", "color:red");
      return;
    }
    //判断数据类型 并进行相应处理
    for (let i = fileList.length; i--; ) {
      if (!(fileList[i] instanceof Blob)) {
        fileList[i] = new Blob([JSON.stringify(fileList[i])], {
          type: "text/plain",
        });
      }
    }
    console.info("blob 转换完成");
    //依据个数 使用下载方式  多于10个会压缩 小于10个会自动下载
    if (num > 10) {
      console.log("文件多于 10 个");
      let jszip = window.JSZip;
      if (!jszip) {
        let info = await this.getjszip();
        console.info(info);
      }
      //建立zip
      let zip = new JSZip();
      fileList.forEach((i, index) => {
        zip.file(nameList[index], i);
      });
      zip
        .generateAsync({
          type: "blob",
        })
        .then((content) => {
          console.info("压缩完成");
          let date = new Date();
          //回调传出
          this.downBlob([content], [date.getMonth() + 1 + "-" + date.getDate() + " " + date.getHours() + ".zip"]);
        });
    } else {
      this.downBlob(fileList, nameList);
    }
  }
  /**
   * 用于JSZip模块的载入
   * @method getjszip
   * @date 2020-08-19
   * @returns {Promise} 返回之后 JSZip 就载入了
   */
  getjszip() {
    return new Promise((res) => {
      let url = "https://cdn.bootcdn.net/ajax/libs/jszip/3.5.0/jszip.min.js";
      let script = document.createElement("script");
      script.onload = () => {
        res("jszip载入成功");
      };
      script.src = url;
      document.body.appendChild(script);
    });
  }
  /**
   * 批量下载 Blob 的函数
   * @method downBlob
   * @date 2020-08-19
   * @param {Array} fileArr Blob数组
   * @param {Array} nameList 名称数组
   */
  downBlob(fileArr, nameList) {
    fileArr.forEach((blob, index) => {
      let Url = URL.createObjectURL(blob);
      let a = document.createElement("a");
      a.href = Url;
      a.download = nameList[index];
      a.click();
      a.remove();
      console.log("完成");
    });
  }
}
